/*   Carina
 *   Copyright (C) 2009 2010 2011 2012  Zdravko Velinov
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// Ye Olde C
#include <cassert>
#include <cstdlib>
#include <cmath>

// STL
#include <map>
#include <stack>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include <exception>
#include <fstream>
#include <functional>

// Project
#include "carina/terrain.hh"
#include "carina/resources.hh"
#include "carina/debug-gui.hh"
#include "carina/common/exception.hh"
#include "carina/scene.hh"
#include "carina/axis.hh"
#include "carina/window.hh"
#include "carina/common/timer.hh"
#include "carina/graphics-engine.hh"
#include "carina/ces-loader.hh"
#include "carina/box.hh"
#include "carina/post-processing.hh"

#ifdef _WIN32
#   define WIN32_LEAN_AND_MEAN 1
#   include <windows.h>
#endif

using namespace Carina;

class System
{
    Timer                    m_Timer;

    GraphicsEngine           m_GraphicsEngine;
    GraphicsEngineContextPtr m_GECtx;
    RendererPtr              m_Renderer;

    
    ScenePtr                 m_Scene;
    PostProcessingPtr        m_PostProcessing;
    GUI::System              m_GUI;

    Axis                     m_Axis;
    FPSCounter               m_FPS;

    float                    m_Velocity;
    enum {
        M_STAY      = 0,
        M_FORWARD   = 1,
        M_BACKWARD  = 1 << 1,
        M_RIGHT     = 1 << 2,
        M_LEFT      = 1 << 3
    };

    bool                m_Status;
    int                 m_Movement;
public:
    System()
        :   m_GECtx(m_GraphicsEngine.getContexts()[0]),
            m_Renderer(m_GECtx->getWindow()->getRenderer()),
            m_Scene(make_aligned_shared<Scene>(m_GECtx)),
            m_PostProcessing(make_aligned_shared<PostProcessing>(m_GECtx)),
            m_GUI(*m_GECtx->getResourceManager()),
            m_Axis(m_GECtx->getResourceManager()),
            m_Velocity(10e-8f),
            m_Status(true),
            m_Movement(M_STAY)
    {
        SUBSCRIBE_EVENT(*m_GECtx->getWindow(), KeyboardEvent, bind(&System::handleKeyDown, this, _1));
        SUBSCRIBE_EVENT(*m_GECtx->getWindow(), MouseMotionEvent, bind(&System::handleMouseMoved, this, _1));
        SUBSCRIBE_EVENT(*m_GECtx->getWindow(), ResizeWindowEvent, bind(&System::handleWindowResize, this, _1));
        SUBSCRIBE_EVENT(*m_GECtx->getWindow(), QuitEvent, bind(&System::handleQuit, this, _1));

        m_Renderer->setClearColor(Vector4(0.0f, 0.7f, 1.0f, 0.0f));

        m_Scene->getCamera()->translate(Vector3(0.0f, 100.0f, 10.0f));

        DrawablePtr terra = make_aligned_shared<Terrain>(m_Scene->getCamera(), m_GECtx->getResourceManager(), "terrain.png", "splat-test.png");
        DrawableNodePtr drawable_node = make_aligned_shared<DrawableNode>(terra);
        m_Scene->getRoot()->addChild(drawable_node);

        auto directional_light = static_pointer_cast<DirectionalLight>(m_GECtx->getLightingManager()->getFactory("Directional light")->second->create());
        directional_light->setColor(Vector3(1.0f, 1.0f, 1.0f));
        directional_light->setDirection(Vector3(0.0f, 0.5f, 0.5f));
        m_Scene->addGlobalLightSource(directional_light);
        m_Scene->setAmbientLight(Vector3(0.3f, 0.3f, 0.3f));

        ShaderProgramPtr prog = m_GECtx->getResourceManager()->getShaderProgram("post_processor_FXAA");
        TextureVarPtr tex_var = prog->getTexture("InputRTColor");
        m_Renderer->useShaderProgram(prog);
        prog->getVariable("rcpFrame")->set(Vector2(1.0f/m_GECtx->getWindow()->getWidth(), 1.0f/m_GECtx->getWindow()->getHeight()));
        m_PostProcessing->registerPostProcProgram(PostProcessingPass(prog, tex_var));

    //    string mesh_name = m_ResMgr.getResource("test_editor.ces");
    //   CESLoader::load(mesh_name, m_ResMgr, m_Lighting, m_NodeMgr, m_World, m_Scene);
    }

    ~System() {}

    void render()
    {
        while(m_Status)
        {
            m_Timer.update();
            float diff = static_cast<float>(m_Timer.getElapsedTime());

            m_GraphicsEngine.updateContexts();
            
            CameraPtr cam = m_Scene->getCamera();
            if(m_Movement)
            {
                if(m_Movement & M_FORWARD)
                    cam->translate(- cam->getForwardVector() * m_Velocity * diff);
                if(m_Movement & M_BACKWARD)
                    cam->translate(cam->getForwardVector() * m_Velocity * diff);
                if(m_Movement & M_RIGHT)
                    cam->translate(cam->getLeftVector() * m_Velocity * diff);
                if(m_Movement & M_LEFT)
                    cam->translate(- cam->getLeftVector() * m_Velocity * diff);
            }

            m_Renderer->clearAll();

            m_PostProcessing->setupFramebuffer();

            m_Scene->draw();
            m_Axis.draw(cam);
            
            m_PostProcessing->postProcess();
            
            m_FPS.update(m_Timer.getTime());

            m_GECtx->swapBuffers();
        }
    }

private:
    bool handleKeyDown(const KeyboardEvent& kevt)
    {
        switch(kevt.getKey())
        {
        case CE_KEY_ESCAPE:
        {
            m_Status = false;
            return true;
        }
        case CE_KEY_W:
        {
            if(kevt.isKeyDown())
                m_Movement |= M_FORWARD;
            else
                m_Movement &= ~M_FORWARD;
            return true;
        } break;
        case CE_KEY_A:
        {
            if(kevt.isKeyDown())
                m_Movement |= M_LEFT;
            else
                m_Movement &= ~M_LEFT;
            return true;
        } break;
        case CE_KEY_S:
        {
            if(kevt.isKeyDown())
                m_Movement |= M_BACKWARD;
            else
                m_Movement &= ~M_BACKWARD;
            return true;
        } break;
        case CE_KEY_D: 
        {
            if(kevt.isKeyDown())
                m_Movement |= M_RIGHT;
            else
                m_Movement &= ~M_RIGHT;
            return true;
        } break;
        default:
            break;
        }
        return false;
    }

    bool handleMouseMoved(const MouseMotionEvent& motion)
    {
        m_Scene->getCamera()->rotate((float)motion.getRelX(), (float)motion.getRelY());
        return true;
    }

    bool handleWindowResize(const ResizeWindowEvent& evt)
    {
        m_Scene->resize(evt.getWidth(), evt.getHeight());
        return true;
    }

    bool handleQuit(const QuitEvent& qevt) { m_Status = false; return true; }
};

int CarinaMain(int argc, char **argv)
{
    try
    {
        System sys;
        sys.render();
    }
    catch(const std::exception& e)
    {
        CE_CRASH(e.what());
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
